package com.xw.repo;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilitySlice;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorGroup;
import ohos.agp.animation.AnimatorProperty;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.*;
import ohos.agp.render.BlurDrawLooper;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.*;
import ohos.agp.window.dialog.PopupDialog;
import ohos.agp.window.service.DisplayManager;
import ohos.agp.window.service.Window;
import ohos.agp.window.service.WindowManager;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.global.resource.NotExistException;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.math.BigDecimal;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Locale;

import static com.xw.repo.BubbleUtils.*;
import static com.xw.repo.BubbleUtils.dp2px;

/**
 * A beautiful and powerful custom seek bar, which has a bubble view with progress
 * appearing upon when seeking. Highly customizable, mostly demands has been considered.
 * <p>
 * Created by woxingxiao on 2016-10-27.
 */
public class BubbleSeekBar extends ComponentContainer implements Component.DrawTask, Component.EstimateSizeListener, ComponentContainer.ArrangeListener {

    static final int NONE = -1;

    @Retention(RetentionPolicy.SOURCE)
    public @interface TextPosition {
        int SIDES = 0, BOTTOM_SIDES = 1, BELOW_SECTION_MARK = 2;
    }

    private float mMin; // min
    private float mMax; // max
    private float mProgress; // real time value
    private boolean isFloatType; // support for float type output
    private int mTrackSize; // height of right-track(on the right of thumb)
    private int mSecondTrackSize; // height of left-track(on the left of thumb)
    private int mThumbRadius; // radius of thumb
    private int mThumbRadiusOnDragging; // radius of thumb when be dragging
    private int mTrackColor; // color of right-track
    private int mSecondTrackColor; // color of left-track
    private int mThumbColor; // color of thumb
    private int mSectionCount; // shares of whole progress(max - min)
    private boolean isShowSectionMark; // show demarcation points or not
    private boolean isAutoAdjustSectionMark; // auto scroll to the nearest section_mark or not
    private boolean isShowSectionText; // show section-text or not
    private int mSectionTextSize; // text size of section-text
    private int mSectionTextColor; // text color of section-text
    @TextPosition
    private int mSectionTextPosition; // text position of section-text relative to track
    private int mSectionTextInterval; // the interval of two section-text
    private boolean isShowThumbText; // show real time progress-text under thumb or not
    private int mThumbTextSize; // text size of progress-text
    private int mThumbTextColor; // text color of progress-text
    private boolean isShowProgressInFloat; // show bubble-progress in float or not
    private boolean isTouchToSeek; // touch anywhere on track to quickly seek
    private boolean isSeekStepSection; // seek one step by one section, the progress is discrete
    private boolean isSeekBySection; // seek by section, the progress may not be linear
    private long mAnimDuration; // duration of animation
    private boolean isAlwaysShowBubble; // bubble shows all time
    private long mAlwaysShowBubbleDelay; // the delay duration before bubble shows all the time
    private boolean isHideBubble; // hide bubble
    private boolean isRtl; // right to left

    private int mBubbleColor;// color of bubble
    private int mBubbleTextSize; // text size of bubble-progress
    private int mBubbleTextColor; // text color of bubble-progress

    private float mDelta; // max - min
    private float mSectionValue; // (mDelta / mSectionCount)
    private float mThumbCenterX; // X coordinate of thumb's center
    private float mTrackLength; // pixel length of whole track
    private float mSectionOffset; // pixel length of one section
    private boolean isThumbOnDragging; // is thumb on dragging or not
    private int mTextSpace; // space between text and track
    private boolean triggerBubbleShowing;
    private HashMap<Integer, String> mSectionTextArray = new HashMap<>();
    private float mPreThumbCenterX;
    private boolean triggerSeekBySection;

    private OnProgressChangedListener mProgressListener; // progress changing listener
    private float mLeft; // space between left of track and left of the view
    private float mRight; // space between right of track and left of the view
    private Paint mPaint;
    private Rect mRectText;

    private WindowManager mWindowManager;
    private BubbleView mBubbleView;
    private int mBubbleRadius;
    private float mBubbleCenterRawSolidX;
    private float mBubbleCenterRawSolidY;
    private float mBubbleCenterRawX;
    private WindowManager.LayoutConfig mLayoutParams;
    private int[] mPoint = new int[2];
    private boolean isTouchToSeekAnimEnd = true;
    private float mPreSecValue; // previous SectionValue
    private BubbleConfigBuilder mConfigBuilder; // config attributes


    private int TOP_BAR_HEIGHT = 129;
    Formatter mFormatter;
    private String mIndicatorFormatter;
    private NumericTransformer mNumericTransformer;
    private StringBuilder mFormatBuilder;
    private PopupIndicator mIndicator;
    private static final int DEFAULT_THUMB_COLOR = 0xff009688;
    private static final String DEFAULT_FORMATTER = "%d";
    private int mAddedTouchBounds;
    private int mValue = 0;

    // 按下的坐标
    private MmiPoint downPoint = new MmiPoint(0f,0f);
    private long downTimestamp = 0;

    public BubbleSeekBar(Context context) {
        this(context, null);
    }

    public BubbleSeekBar(Context context, AttrSet attrs) {
        this(context, attrs, "0");
    }

    public BubbleSeekBar(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mMin = getAttrSetFloat(attrs, "bsb_min", 0.0f);
        mMax = getAttrSetFloat(attrs, "bsb_max", 100.0f);
        mProgress = getAttrSetFloat(attrs, "bsb_progress", mMin);
        isFloatType = getAttrSetBoolean(attrs, "bsb_is_float_type", false);
        mTrackSize = getAttrSetInt(attrs, "bsb_track_size", dp2px(getContext(), 2));
        mSecondTrackSize = getAttrSetInt(attrs, "bsb_second_track_size", mTrackSize + dp2px(getContext(), 2));
        mThumbRadius = getAttrSetInt(attrs, "bsb_thumb_radius", mSecondTrackSize + dp2px(getContext(), 2));
        mThumbRadiusOnDragging = getAttrSetInt(attrs, "bsb_thumb_radius_on_dragging", mSecondTrackSize * 2);
        mSectionCount = getAttrSetInt(attrs, "bsb_section_count", 10);
        try {
            mTrackColor = getAttrSetColor(attrs, "bsb_track_color", new Color(getContext().getResourceManager().getElement(ResourceTable.Color_colorPrimary).getColor())).getValue();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (NotExistException ex) {
            ex.printStackTrace();
        } catch (WrongTypeException ex) {
            ex.printStackTrace();
        }
        try {
            mSecondTrackColor = getAttrSetColor(attrs, "bsb_second_track_color", new Color(getContext().getResourceManager().getElement(ResourceTable.Color_colorAccent).getColor())).getValue();
        } catch (IOException ex) {
            ex.printStackTrace();
        } catch (NotExistException ex) {
            ex.printStackTrace();
        } catch (WrongTypeException ex) {
            ex.printStackTrace();
        }
        mThumbColor = getAttrSetInt(attrs, "bsb_thumb_color", mSecondTrackColor);
        isShowSectionText = getAttrSetBoolean(attrs, "bsb_show_section_text", false);
        mSectionTextSize = getAttrSetInt(attrs, "bsb_section_text_size", sp2px(getContext(), 14));
        mSectionTextColor = getAttrSetInt(attrs, "bsb_section_text_color", mTrackColor);
        isSeekStepSection = getAttrSetBoolean(attrs, "bsb_seek_step_section", false);
        isSeekBySection = getAttrSetBoolean(attrs, "bsb_seek_by_section", false);
        String pos = getAttrSetString(attrs, "bsb_section_text_position", "NONE");
        if (pos.equals("sides")) {
            mSectionTextPosition = TextPosition.SIDES;
        } else if (pos.equals("bottom_sides")) {
            mSectionTextPosition = TextPosition.BOTTOM_SIDES;
        } else if (pos.equals("below_section_mark")) {
            mSectionTextPosition = TextPosition.BELOW_SECTION_MARK;
        } else {
            mSectionTextPosition = NONE;
        }
        mSectionTextInterval = getAttrSetInt(attrs, "bsb_section_text_interval", 1);
        isShowThumbText = getAttrSetBoolean(attrs, "bsb_show_thumb_text", false);
        mThumbTextSize = getAttrSetInt(attrs, "bsb_thumb_text_size", sp2px(getContext(), 14));
        mThumbTextColor = getAttrSetInt(attrs, "bsb_thumb_text_color", mSecondTrackColor);
        mBubbleColor = getAttrSetColor(attrs, "bsb_bubble_color", new Color(mSecondTrackColor)).getValue();
        mBubbleTextSize = getAttrSetInt(attrs, "bsb_bubble_text_size", sp2px(getContext(), 14));
        mBubbleTextColor = getAttrSetColor(attrs, "bsb_bubble_text_color", new Color(Color.WHITE.getValue())).getValue();
        isShowSectionMark = getAttrSetBoolean(attrs, "bsb_show_section_mark", false);
        isAutoAdjustSectionMark = getAttrSetBoolean(attrs, "bsb_auto_adjust_section_mark", false);
        isShowProgressInFloat = getAttrSetBoolean(attrs, "bsb_show_progress_in_float", false);
        int duration = getAttrSetInt(attrs, "bsb_anim_duration", -1);
        mAnimDuration = duration < 0 ? 200 : duration;
        isTouchToSeek = getAttrSetBoolean(attrs, "bsb_touch_to_seek", false);
        isAlwaysShowBubble = getAttrSetBoolean(attrs, "bsb_always_show_bubble", false);
        duration = getAttrSetInt(attrs, "_bsb_always_show_bubble_delay", 0);
        mAlwaysShowBubbleDelay = duration < 0 ? 0 : duration;
        isHideBubble = getAttrSetBoolean(attrs, "bsb_hide_bubble", false);
        isRtl = getAttrSetBoolean(attrs, "bsb_rtl", false);
        int touchBounds = dp2px(getContext(),32);
        mAddedTouchBounds = Math.max(touchBounds, mThumbTextSize) / 2;

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        mPaint.setTextAlign(TextAlignment.CENTER);

        mRectText = new Rect();
        mTextSpace = dp2px(getContext(), 2);


        TOP_BAR_HEIGHT = AttrHelper.vp2px(15f,context);
        int height=DisplayManager.getInstance().getDefaultDisplay(context).get().getAttributes().height;
        if(height==2211){
            TOP_BAR_HEIGHT = AttrHelper.vp2px(30f,context);
        }

        initConfigByPriority();
        setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
                float x = point.getX();
                float y = point.getY();
                int index = getIndexFromX(getTouchX(touchEvent, 0));
                switch (touchEvent.getAction()) {
                    case TouchEvent.PRIMARY_POINT_DOWN:
                        downPoint = point;
                        downTimestamp = System.currentTimeMillis();
                        simulateClick();
//                            getParent().requestDisallowInterceptTouchEvent(true);
                        isThumbOnDragging = isThumbTouched(x, y);
                        if (mValue != index + mMin) {
                            mValue = (int) (index + mMin);
                        }

                        if (isThumbOnDragging) {
                            if (isSeekBySection && !triggerSeekBySection) {
                                triggerSeekBySection = true;
                            }
//                            if (isAlwaysShowBubble && !triggerBubbleShowing) {
//                                triggerBubbleShowing = true;
//                            }
                            if (!isHideBubble) {
                                showBubble(0);
                            }
                            invalidate();
                        } else if (isTouchToSeek && isTrackTouched(x, y)) {
                            isThumbOnDragging = true;
                            if (isSeekBySection && !triggerSeekBySection) {
                                triggerSeekBySection = true;
                            }
//                            if (isAlwaysShowBubble) {
//                                hideBubble();
//                                triggerBubbleShowing = true;
//                            }

                            if (isSeekStepSection) {
                                mThumbCenterX = mPreThumbCenterX = calThumbCxWhenSeekStepSection(x);
                            } else {
                                mThumbCenterX = x;
                                if (mThumbCenterX < mLeft) {
                                    mThumbCenterX = mLeft;
                                }
                                if (mThumbCenterX > mRight) {
                                    mThumbCenterX = mRight;
                                }
                            }

                            mProgress = calculateProgress();

                            if (!isHideBubble) {
                                mBubbleCenterRawX = calculateCenterRawXofBubbleView();
                                showBubble(0);
                            }

                            invalidate();
                        }

                        dx = mThumbCenterX - x;

                        break;
                    case TouchEvent.POINT_MOVE:
                        if (isThumbOnDragging) {
                            boolean flag = true;

                            if (isSeekStepSection) {
                                float x0 = (float) calThumbCxWhenSeekStepSection(x);
                                if (x0 != mPreThumbCenterX) {
                                    mThumbCenterX = mPreThumbCenterX = x0;
                                } else {
                                    flag = false;
                                }
                            } else {
                                mThumbCenterX = x + dx;
                                if (mThumbCenterX < mLeft) {
                                    mThumbCenterX = mLeft;
                                }
                                if (mThumbCenterX > mRight) {
                                    mThumbCenterX = mRight;
                                }
                            }
                            mBubbleCenterRawX = calculateCenterRawXofBubbleView();

                            if (flag) {
                                mProgress = calculateProgress();
                                if (!isHideBubble) {
                                    if (mValue != index + mMin) {
                                        mValue = (int) (index + mMin);
                                    }
                                    mIndicator.refresh(mThumbCenterX+50, getDialogY(),  isShowProgressInFloat ?
                                            String.valueOf(getProgressFloat()) : String.valueOf(getProgress()));
//                                    mBubbleCenterRawX = calculateCenterRawXofBubbleView();
//                                    mLayoutParams.x = (int) (mBubbleCenterRawX + 0.5f);
//                                    mWindowManager.addComponent(mBubbleView, getContext(),0);
//                                    mBubbleView.setProgressText(isShowProgressInFloat ?
//                                            String.valueOf(getProgressFloat()) : String.valueOf(getProgress()));
                                } else {
                                    processProgress();
                                }
                                invalidate();
                                if (mProgressListener != null) {
                                    mProgressListener.onProgressChanged(BubbleSeekBar.this, getProgress(), getProgressFloat(), true);
                                }
                            }
                        }
                        break;
                    case TouchEvent.PRIMARY_POINT_UP:
                    case TouchEvent.CANCEL:
                        if (mIndicator != null) {
                            // 间隔时间
                            int intervalTime = (int) (System.currentTimeMillis() - downTimestamp);
                            // 点击事件
                            if (downPoint.getX() == point.getX() &&
                                    downPoint.getY() == point.getY() &&
                                    intervalTime <= 100 &&
                                    Math.abs(mThumbCenterX-downPoint.getX()) <= 50
                            ) {

                                // 显示弹框
                                if (!isHideBubble) {
                                    mBubbleCenterRawX = calculateCenterRawXofBubbleView();
                                    showBubble(10);
                                }
                                // 延迟关闭
                                context.getUITaskDispatcher().delayDispatch(() -> {
                                    mIndicator.animateToNormal();
                                },300);
                            }else{
                                mIndicator.animateToNormal();
                            }
                        }
                        if (isAutoAdjustSectionMark) {
                            if (isTouchToSeek) {
                                eventHandler.postTask(new Runnable() {
                                    @Override
                                    public void run() {
                                        isTouchToSeekAnimEnd = false;
                                        autoAdjustSection();
                                    }
                                }, mAnimDuration);
                            } else {
                                autoAdjustSection();
                            }
                        } else if (isThumbOnDragging || isTouchToSeek) {
                            if (isHideBubble) {
                                createAnimatorProperty()
                                        .setDuration(mAnimDuration)
                                        .setDelay(!isThumbOnDragging && isTouchToSeek ? 300 : 0)
                                        .setStateChangedListener(new Animator.StateChangedListener() {
                                            @Override
                                            public void onStart(Animator animator) {

                                            }

                                            @Override
                                            public void onStop(Animator animator) {

                                            }

                                            @Override
                                            public void onCancel(Animator animator) {
                                                isThumbOnDragging = false;
                                                invalidate();
                                            }

                                            @Override
                                            public void onEnd(Animator animator) {
                                                isThumbOnDragging = false;
                                                invalidate();
                                            }

                                            @Override
                                            public void onPause(Animator animator) {

                                            }

                                            @Override
                                            public void onResume(Animator animator) {

                                            }
                                        }).start();
                            } else {
                                eventHandler.postTask(new Runnable() {
                                    @Override
                                    public void run() {
                                        isThumbOnDragging = false;
                                        invalidate();
                                    }
                                }, mAnimDuration);
                            }
                        }

                        if (mProgressListener != null) {
                            mProgressListener.onProgressChanged(BubbleSeekBar.this, getProgress(), getProgressFloat(), true);
                            mProgressListener.getProgressOnActionUp(BubbleSeekBar.this, getProgress(), getProgressFloat());
                        }

                        break;
                    default:
                        break;
                }


                return true;

            }
        });


        if (isHideBubble) {
            return;
        } else {
            mWindowManager = WindowManager.getInstance();
            // init BubbleView
            mBubbleView = new BubbleView(context);
            mBubbleView.setProgressText(isShowProgressInFloat ?
                    String.valueOf(getProgressFloat()) : String.valueOf(getProgress()));

            mLayoutParams = new WindowManager.LayoutConfig();
            mLayoutParams.alignment = TextAlignment.START | TextAlignment.TOP;
            mLayoutParams.width = WindowManager.LayoutConfig.MARK_FULL_SCREEN;
            mLayoutParams.height = WindowManager.LayoutConfig.MARK_FULL_SCREEN;
            mLayoutParams.flags = WindowManager.LayoutConfig.MARK_TOUCH_MODAL_IMPOSSIBLE
                    | WindowManager.LayoutConfig.MARK_WATCH_OUTSIDE_TOUCH;
            mLayoutParams.type = WindowManager.LayoutConfig.MOD_TOAST;
            calculateRadiusOfBubble();
            setNumericTransformer(new DefaultNumericTransformer());
            mIndicator = new PopupIndicator(this, null, calculateMarkerWidth(), mBubbleRadius, sp2px(getContext(), 5));
        }
        setArrangeListener(this);
        setEstimateSizeListener(this);
        invalidate();

    }

    private void initConfigByPriority() {
        if (mMin == mMax) {
            mMin = 0.0f;
            mMax = 100.0f;
        }
        if (mMin > mMax) {
            float tmp = mMax;
            mMax = mMin;
            mMin = tmp;
        }
        if (mProgress < mMin) {
            mProgress = mMin;
        }
        if (mProgress > mMax) {
            mProgress = mMax;
        }
        if (mSecondTrackSize < mTrackSize) {
            mSecondTrackSize = mTrackSize + dp2px(getContext(), 2);
        }
        if (mThumbRadius <= mSecondTrackSize) {
            mThumbRadius = mSecondTrackSize + dp2px(getContext(), 2);
        }
        if (mThumbRadiusOnDragging <= mSecondTrackSize) {
            mThumbRadiusOnDragging = mSecondTrackSize * 2;
        }
        if (mSectionCount <= 0) {
            mSectionCount = 10;
        }
        mDelta = mMax - mMin;
        mSectionValue = mDelta / mSectionCount;

        if (mSectionValue < 1) {
            isFloatType = true;
        }
        if (isFloatType) {
            isShowProgressInFloat = true;
        }
        if (mSectionTextPosition != NONE) {
            isShowSectionText = true;
        }
        if (isShowSectionText) {
            if (mSectionTextPosition == NONE) {
                mSectionTextPosition = TextPosition.SIDES;
            }
            if (mSectionTextPosition == TextPosition.BELOW_SECTION_MARK) {
                isShowSectionMark = true;
            }
        }
        if (mSectionTextInterval < 1) {
            mSectionTextInterval = 1;
        }

        initSectionTextArray();

        if (isSeekStepSection) {
            isSeekBySection = false;
            isAutoAdjustSectionMark = false;
        }
        if (isAutoAdjustSectionMark && !isShowSectionMark) {
            isAutoAdjustSectionMark = false;
        }
        if (isSeekBySection) {
            mPreSecValue = mMin;
            if (mProgress != mMin) {
                mPreSecValue = mSectionValue;
            }
            isShowSectionMark = true;
            isAutoAdjustSectionMark = true;
        }
        if (isHideBubble) {
            isAlwaysShowBubble = false;
        }
        if (isAlwaysShowBubble) {
            setProgress(mProgress);
        }

        mThumbTextSize = isFloatType || isSeekBySection || (isShowSectionText && mSectionTextPosition ==
                TextPosition.BELOW_SECTION_MARK) ? mSectionTextSize : mThumbTextSize;
    }

    /**
     * Calculate radius of bubble according to the Min and the Max
     */
    private void calculateRadiusOfBubble() {
        mPaint.setTextSize(mBubbleTextSize);

        // 计算滑到两端气泡里文字需要显示的宽度，比较取最大值为气泡的半径
        String text;
        if (isShowProgressInFloat) {
            text = float2String(isRtl ? mMax : mMin);
        } else {
            if (isRtl) {
                text = isFloatType ? float2String(mMax) : String.valueOf((int) mMax);
            } else {
                text = isFloatType ? float2String(mMin) : String.valueOf((int) mMin);
            }
        }
        mPaint.getTextBounds(text);
        int w1 = (mRectText.getWidth() + mTextSpace * 2) >> 1;

        if (isShowProgressInFloat) {
            text = float2String(isRtl ? mMin : mMax);
        } else {
            if (isRtl) {
                text = isFloatType ? float2String(mMin) : String.valueOf((int) mMin);
            } else {
                text = isFloatType ? float2String(mMax) : String.valueOf((int) mMax);
            }
        }
        mPaint.getTextBounds(text);
        int w2 = (mRectText.getWidth() + mTextSpace * 2) >> 1;

        mBubbleRadius = dp2px(getContext(), 14); // default 14dp
        int max = Math.max(mBubbleRadius, Math.max(w1, w2));
        mBubbleRadius = max + mTextSpace;
    }

    private void initSectionTextArray() {
        boolean ifBelowSection = mSectionTextPosition == TextPosition.BELOW_SECTION_MARK;
        boolean ifInterval = mSectionTextInterval > 1 && mSectionCount % 2 == 0;
        float sectionValue;
        for (int i = 0; i <= mSectionCount; i++) {
            sectionValue = isRtl ? mMax - mSectionValue * i : mMin + mSectionValue * i;

            if (ifBelowSection) {
                if (ifInterval) {
                    if (i % mSectionTextInterval == 0) {
                        sectionValue = isRtl ? mMax - mSectionValue * i : mMin + mSectionValue * i;
                    } else {
                        continue;
                    }
                }
            } else {
                if (i != 0 && i != mSectionCount) {
                    continue;
                }
            }
            mSectionTextArray.put(i, isFloatType ? float2String(sectionValue) : (int) sectionValue + "");
        }
    }


    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        if (!isHideBubble) {
            locatePositionInWindow();
        }
        return false;
    }
    public static int resolveSize(int size, int measureSpec) {
        int result;
        int specMode = EstimateSpec.getMode(measureSpec);
        int specSize = EstimateSpec.getSize(measureSpec);
        switch (specMode) {
            case EstimateSpec.NOT_EXCEED:
                if (specSize < size) {
                    result = specSize;
                } else {
                    result = size;
                }
                break;
            case EstimateSpec.PRECISE:
                result = Math.min(size, specSize);
                break;
            case EstimateSpec.UNCONSTRAINT:
            default:
                result = size;
        }
        return result;
    }

    /**
     * In fact there two parts of the BubbleSeeBar, they are the BubbleView and the SeekBar.
     * <p>
     * The BubbleView is added to Window by the WindowManager, so the only connection between
     * BubbleView and SeekBar is their origin raw coordinates on the screen.
     * <p>
     * It's easy to compute the coordinates(mBubbleCenterRawSolidX, mBubbleCenterRawSolidY) of point
     * when the Progress equals the Min. Then compute the pixel length increment when the Progress is
     * changing, the result is mBubbleCenterRawX. At last the WindowManager calls updateViewLayout()
     * to update the LayoutParameter.x of the BubbleView.
     * <p>
     * 气泡BubbleView实际是通过WindowManager动态添加的一个视图，因此与SeekBar唯一的位置联系就是它们在屏幕上的
     * 绝对坐标。
     * 先计算进度mProgress为mMin时BubbleView的中心坐标（mBubbleCenterRawSolidX，mBubbleCenterRawSolidY），
     * 然后根据进度来增量计算横坐标mBubbleCenterRawX，再动态设置LayoutParameter.x，就实现了气泡跟随滑动移动。
     */
    private void locatePositionInWindow() {
        mPoint = getLocationOnScreen();
//        ComponentParent parent = getComponentParent();
//        if (parent instanceof Component && ((Component) parent).getEstimatedWidth() > 0) {
//            mPoint[0] %= ((Component) parent).getEstimatedWidth();
//        }

        if (isRtl) {
            mBubbleCenterRawSolidX = mPoint[0] + mRight ;
        } else {
            mBubbleCenterRawSolidX = mPoint[0] + mLeft;
        }
        mBubbleCenterRawX = calculateCenterRawXofBubbleView();
        mBubbleCenterRawSolidY = mPoint[1] - mBubbleView.getEstimatedHeight();
        mBubbleCenterRawSolidY -= dp2px(getContext(), 24);
    }

    @Override
    public void invalidate() {
        super.invalidate();
        addDrawTask(this::onDraw);
    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int height = mThumbRadiusOnDragging * 2; // 默认高度为拖动时thumb圆的直径
        if (isShowThumbText) {
            mPaint.setTextSize(mThumbTextSize);
            mRectText = mPaint.getTextBounds("j"); // j is the highest of all letters and numbers
            height += mRectText.getHeight(); // 如果显示实时进度，则原来基础上加上进度文字高度和间隔
        }
        if (isShowSectionText && mSectionTextPosition >= BubbleSeekBar.TextPosition.BOTTOM_SIDES) { // 如果Section值在track之下显示，比较取较大值
            mPaint.setTextSize(mSectionTextSize);
            mRectText = mPaint.getTextBounds("j");
            height = Math.max(height, mThumbRadiusOnDragging * 2 + mRectText.getHeight());
        }
        height += mTextSpace * 2;
        setEstimatedSize(
                Component.EstimateSpec.getChildSizeWithMode(resolveSize(dp2px(getContext(), 180), widthMeasureSpec), widthMeasureSpec, EstimateSpec.PRECISE),
                Component.EstimateSpec.getChildSizeWithMode(height, height, Component.EstimateSpec.NOT_EXCEED));
        mLeft = getPaddingLeft() + mThumbRadiusOnDragging;
        mRight = getEstimatedWidth() - getPaddingRight() - mThumbRadiusOnDragging;
        if (isShowSectionText) {
            mPaint.setTextSize(mSectionTextSize);

            if (mSectionTextPosition == BubbleSeekBar.TextPosition.SIDES) {
                String text = mSectionTextArray.get(0);
                mRectText = mPaint.getTextBounds(text);
                mLeft += (mRectText.getWidth() + mTextSpace);

                text = mSectionTextArray.get(mSectionCount);
                mRectText = mPaint.getTextBounds(text);
                mRight -= (mRectText.getWidth() + mTextSpace);
            } else if (mSectionTextPosition >= BubbleSeekBar.TextPosition.BOTTOM_SIDES) {
                String text = mSectionTextArray.get(0);
                mRectText = mPaint.getTextBounds(text);
                float max = Math.max(mThumbRadiusOnDragging, mRectText.getWidth() / 2f);
                mLeft = getPaddingLeft() + max + mTextSpace;

                text = mSectionTextArray.get(mSectionCount);
                mRectText = mPaint.getTextBounds(text);
                max = Math.max(mThumbRadiusOnDragging, mRectText.getWidth() / 2f);
                mRight = getEstimatedWidth() - getPaddingRight() - max - mTextSpace;
            }
        } else if (isShowThumbText && mSectionTextPosition == NONE) {
            mPaint.setTextSize(mThumbTextSize);

            String text = mSectionTextArray.get(0);
            mRectText = mPaint.getTextBounds(text);
            float max = Math.max(mThumbRadiusOnDragging, mRectText.getWidth() / 2f);
            mLeft = getPaddingLeft() + max + mTextSpace;

            text = mSectionTextArray.get(mSectionCount);
            mRectText = mPaint.getTextBounds(text);
            max = Math.max(mThumbRadiusOnDragging, mRectText.getWidth() / 2f);
            mRight = getEstimatedWidth() - getPaddingRight() - max - mTextSpace;
        }

        mTrackLength = mRight - mLeft;
        mSectionOffset = mTrackLength * 1f / mSectionCount;

        if (!isHideBubble) {
            mBubbleView.estimateSize(widthMeasureSpec, heightMeasureSpec);
        }

        return true;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        float xLeft = getPaddingLeft();
        float xRight = getEstimatedWidth() - getPaddingRight();
        float yTop = getPaddingTop() + mThumbRadiusOnDragging;

        // draw sectionText SIDES or BOTTOM_SIDES
        if (isShowSectionText) {
            mPaint.setColor(new Color(mSectionTextColor));
            mPaint.setTextSize(mSectionTextSize);
            mRectText = mPaint.getTextBounds("0123456789"); // compute solid height

            if (mSectionTextPosition == BubbleSeekBar.TextPosition.SIDES) {
                float y_ = yTop + mRectText.getHeight() / 2f;

                String text = mSectionTextArray.get(0);
                mRectText = mPaint.getTextBounds(text);
                canvas.drawText(mPaint, text, xLeft + mRectText.getWidth() / 2f, y_);
                xLeft += mRectText.getWidth() + mTextSpace;

                text = mSectionTextArray.get(mSectionCount);
                mRectText = mPaint.getTextBounds(text);
                canvas.drawText(mPaint, text, xRight - (mRectText.getWidth() + 0.5f) / 2f, y_);
                xRight -= (mRectText.getWidth() + mTextSpace);

            } else if (mSectionTextPosition >= BubbleSeekBar.TextPosition.BOTTOM_SIDES) {
                float y_ = yTop + mThumbRadiusOnDragging + mTextSpace;

                String text = mSectionTextArray.get(0);
                mRectText = mPaint.getTextBounds(text);
                y_ += mRectText.getHeight();
                xLeft = mLeft;
                if (mSectionTextPosition == BubbleSeekBar.TextPosition.BOTTOM_SIDES) {
                    canvas.drawText(mPaint, text, xLeft, y_);
                }

                text = mSectionTextArray.get(mSectionCount);
                mRectText = mPaint.getTextBounds(text);
                xRight = mRight;
                if (mSectionTextPosition == BubbleSeekBar.TextPosition.BOTTOM_SIDES) {
                    canvas.drawText(mPaint, text, xRight, y_);
                }
            }
        } else if (isShowThumbText && mSectionTextPosition == NONE) {
            xLeft = mLeft;
            xRight = mRight;
        }

        if ((!isShowSectionText && !isShowThumbText) || mSectionTextPosition == BubbleSeekBar.TextPosition.SIDES) {
            xLeft += mThumbRadiusOnDragging;
            xRight -= mThumbRadiusOnDragging;
        }

        boolean isShowTextBelowSectionMark = isShowSectionText && mSectionTextPosition ==
                BubbleSeekBar.TextPosition.BELOW_SECTION_MARK;

        // draw sectionMark & sectionText BELOW_SECTION_MARK
        if (isShowTextBelowSectionMark || isShowSectionMark) {
            mPaint.setTextSize(mSectionTextSize);
            mRectText = mPaint.getTextBounds("0123456789"); // compute solid height

            float x_;
            float y_ = yTop + mRectText.getHeight() + mThumbRadiusOnDragging + mTextSpace;
            float r = (mThumbRadiusOnDragging - dp2px(getContext(), 2)) / 2f;
            float junction; // where secondTrack meets firstTrack
            if (isRtl) {
                junction = mRight - mTrackLength / mDelta * Math.abs(mProgress - mMin);
            } else {
                junction = mLeft + mTrackLength / mDelta * Math.abs(mProgress - mMin);
            }

            for (int i = 0; i <= mSectionCount; i++) {
                x_ = xLeft + i * mSectionOffset;
                if (isRtl) {
                    mPaint.setColor(new Color(x_ <= junction ? mTrackColor : mSecondTrackColor));
                } else {
                    mPaint.setColor(new Color(x_ <= junction ? mSecondTrackColor : mTrackColor));
                }
                // sectionMark

                canvas.drawCircle(x_, yTop, r, mPaint);

                // sectionText belows section
                if (isShowTextBelowSectionMark) {
                    mPaint.setColor(new Color(mSectionTextColor));
                    if (mSectionTextArray.get(i) != null) {
                        canvas.drawText(mPaint, mSectionTextArray.get(i), x_, y_);
                    }
                }
            }
        }

        if (!isThumbOnDragging) {
            if (isRtl) {
                mThumbCenterX = xRight - mTrackLength / mDelta * (mProgress - mMin);
            } else {
                mThumbCenterX = xLeft + mTrackLength / mDelta * (mProgress - mMin);
            }
        }

        // draw thumbText
        if (isShowThumbText && !isThumbOnDragging && isTouchToSeekAnimEnd) {
            mPaint.setColor(new Color(mThumbTextColor));
            mPaint.setTextSize(mThumbTextSize);
            mRectText = mPaint.getTextBounds("0123456789"); // compute solid height
            float y_ = yTop + mRectText.getHeight() + mThumbRadiusOnDragging + mTextSpace;

            if (isFloatType || (isShowProgressInFloat && mSectionTextPosition == BubbleSeekBar.TextPosition.BOTTOM_SIDES &&
                    mProgress != mMin && mProgress != mMax)) {
                canvas.drawText(mPaint, String.valueOf(getProgressFloat()), mThumbCenterX, y_);
            } else {
                canvas.drawText(mPaint, String.valueOf(getProgress()), mThumbCenterX, y_);
            }
        }

        // draw track
        mPaint.setColor(new Color(mSecondTrackColor));
        mPaint.setStrokeWidth(mSecondTrackSize);
        if (isRtl) {
            canvas.drawLine(new Point(xRight, yTop), new Point(mThumbCenterX, yTop), mPaint);
        } else {
            canvas.drawLine(new Point(xLeft, yTop), new Point(mThumbCenterX, yTop), mPaint);
        }

        // draw second track
        mPaint.setColor(new Color(mTrackColor));
        mPaint.setStrokeWidth(mTrackSize);
        if (isRtl) {
            canvas.drawLine(new Point(mThumbCenterX, yTop), new Point(xLeft, yTop), mPaint);
        } else {
            canvas.drawLine(new Point(mThumbCenterX, yTop), new Point(xRight, yTop), mPaint);
        }

        // draw thumb
        mPaint.setColor(new Color(mThumbColor));
        canvas.drawCircle(mThumbCenterX, yTop, isThumbOnDragging ? mThumbRadiusOnDragging : mThumbRadius, mPaint);
    }


    protected void onSizeChanged() {
        eventHandler.postTask(new Runnable() {
            @Override
            public void run() {
                postLayout();
            }
        });
    }
//
//    @Override
//    protected void onVisibilityChanged(@NonNull View changedView, int visibility) {
//        if (isHideBubble || !isAlwaysShowBubble)
//            return;
//
//        if (visibility != VISIBLE) {
//            hideBubble();
//        } else {
//            if (triggerBubbleShowing) {
//                showBubble();
//            }
//        }
//        super.onVisibilityChanged(changedView, visibility);
//    }
//
//    @Override
//    protected void onDetachedFromWindow() {
//        hideBubble();
//        super.onDetachedFromWindow();
//    }
//
//    @Override
//    public boolean simulateClick() {
//        return super.simulateClick();
//    }

    float dx;



    /**
     * Detect effective touch of thumb
     *
     * @param x0 float
     * @param y0 float
     * @return boolean
     */
    private boolean isThumbTouched(float x0, float y0) {
        if (!isEnabled())
            return false;
        float distance = mTrackLength / mDelta * (mProgress - mMin);
        float x = isRtl ? mRight - distance : mLeft + distance;
        float y = getEstimatedHeight() / 2f;
        return (x0 - x) * (x0 - x) + (y0 - y) * (y0 - y)
                <= (mLeft + dp2px(getContext(), 8)) * (mLeft + dp2px(getContext(), 8));
    }

    /**
     * Detect effective touch of track
     *
     * @param x0 float
     * @param y0 float
     * @return boolean
     */
    private boolean isTrackTouched(float x0, float y0) {
        return isEnabled() && x0 >= getPaddingLeft() && x0 <= getWidth() - getPaddingRight()
                && y0 >= getPaddingTop() && y0 <= getHeight() - getPaddingBottom();
    }

    /**
     * If the thumb is being dragged, calculate the thumbCenterX when the seek_step_section is true.
     *
     * @param touchedX float
     * @return float
     */
    private float calThumbCxWhenSeekStepSection(float touchedX) {
        if (touchedX <= mLeft) return mLeft;
        if (touchedX >= mRight) return mRight;

        int i;
        float x = 0;
        for (i = 0; i <= mSectionCount; i++) {
            x = i * mSectionOffset + mLeft;
            if (x <= touchedX && touchedX - x <= mSectionOffset) {
                break;
            }
        }

        if (touchedX - x <= mSectionOffset / 2f) {
            return x;
        } else {
            return (i + 1) * mSectionOffset + mLeft;
        }
    }

    /**
     * Auto scroll to the nearest section mark
     */
    private void autoAdjustSection() {
        int i;
        float x = 0;
        for (i = 0; i <= mSectionCount; i++) {
            x = i * mSectionOffset + mLeft;
            if (x <= mThumbCenterX && mThumbCenterX - x <= mSectionOffset) {
                break;
            }
        }

        BigDecimal bigDecimal = BigDecimal.valueOf(mThumbCenterX);
        float x_ = bigDecimal.setScale(1, BigDecimal.ROUND_HALF_UP).floatValue();
        boolean onSection = x_ == x; // 就在section处，不作valueAnim，优化性能

        AnimatorGroup animatorSet = new AnimatorGroup();

        AnimatorValue valueAnim = null;
        if (!onSection) {
            valueAnim = new AnimatorValue();
            valueAnim.setCurveType(Animator.CurveType.LINEAR);
//            if (mThumbCenterX - x <= mSectionOffset / 2f) {
//                valueAnim = ValueAnimator.ofFloat(mThumbCenterX, x);
//            } else {
//                valueAnim = ValueAnimator.ofFloat(mThumbCenterX, (i + 1) * mSectionOffset + mLeft);
//            }
//            valueAnim.setInterpolator(new LinearInterpolator());
            float finalX = x;
            int finalI = i;
            valueAnim.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float v) {
                    if (mThumbCenterX - finalX <= mSectionOffset / 2f) {
                        mThumbCenterX = mThumbCenterX + (finalX - mThumbCenterX) * v;
                    } else {
                        mThumbCenterX = mThumbCenterX + ((finalI + 1) * mSectionOffset + mLeft - mThumbCenterX) * v;
                    }
                    mBubbleCenterRawX = calculateCenterRawXofBubbleView();
                    mProgress = calculateProgress();
                    processProgress();
                    invalidate();
                    if (mProgressListener != null) {
                        mProgressListener.onProgressChanged(BubbleSeekBar.this, getProgress(),
                                getProgressFloat(), true);
                    }
                }
            });
//            valueAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//                @Override
//                public void onAnimationUpdate(ValueAnimator animation) {
//                    mThumbCenterX = (float) animation.getAnimatedValue();
//                    mProgress = calculateProgress();
//
//                    if (!isHideBubble && mBubbleView.getParent() != null) {
//                        mBubbleCenterRawX = calculateCenterRawXofBubbleView();
//                        mLayoutParams.x = (int) (mBubbleCenterRawX + 0.5f);
//                        mWindowManager.updateViewLayout(mBubbleView, mLayoutParams);
//                        mBubbleView.setProgressText(isShowProgressInFloat ?
//                                String.valueOf(getProgressFloat()) : String.valueOf(getProgress()));
//                    } else {
//                        processProgress();
//                    }
//
//                    invalidate();
//
//                    if (mProgressListener != null) {
//                        mProgressListener.onProgressChanged(BubbleSeekBar.this, getProgress(),
//                                getProgressFloat(), true);
//                    }
//                }
//            });
        }

//        if (isHideBubble) {
        if (!onSection) {
            animatorSet.setDuration(mAnimDuration);
            animatorSet.runParallel(valueAnim);
        }
//        } else {
//            ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mBubbleView, View.ALPHA, isAlwaysShowBubble ? 1 : 0);
//            if (onSection) {
//                animatorSet.setDuration(mAnimDuration).play(alphaAnim);
//            } else {
//                animatorSet.setDuration(mAnimDuration).playTogether(valueAnim, alphaAnim);
//            }
//        }
        animatorSet.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {

            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {
                if (!isHideBubble && !isAlwaysShowBubble) {
                    hideBubble();
                }

                mProgress = calculateProgress();
                isThumbOnDragging = false;
                isTouchToSeekAnimEnd = true;
                invalidate();
            }

            @Override
            public void onEnd(Animator animator) {
                if (!isHideBubble && !isAlwaysShowBubble) {
                    hideBubble();
                }

                mProgress = calculateProgress();
                isThumbOnDragging = false;
                isTouchToSeekAnimEnd = true;
                invalidate();

                if (mProgressListener != null) {
                    mProgressListener.getProgressOnFinally(BubbleSeekBar.this, getProgress(),
                            getProgressFloat(), true);
                }
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });
        animatorSet.start();
    }

    /**
     * Showing the Bubble depends the way that the WindowManager adds a Toast type view to the Window.
     * <p>
     * 显示气泡
     * 原理是利用WindowManager动态添加一个与Toast相同类型的BubbleView，消失时再移除
     */
    private void showBubble(long duration) {
        mIndicator.animateToPressed(duration);
        mIndicator.refresh(mThumbCenterX+50, getDialogY(),  isShowProgressInFloat ?
                String.valueOf(getProgressFloat()) : String.valueOf(getProgress()));
    }

    /**
     * The WindowManager removes the BubbleView from the Window.
     */
    private void hideBubble() {

    }

    private String float2String(float value) {
        return String.valueOf(formatFloat(value));
    }

    private float formatFloat(float value) {
        BigDecimal bigDecimal = BigDecimal.valueOf(value);
        return bigDecimal.setScale(1, BigDecimal.ROUND_HALF_UP).floatValue();
    }

    private float calculateCenterRawXofBubbleView() {
        if (isRtl) {
            return mBubbleCenterRawSolidX - mTrackLength * (mProgress - mMin) / mDelta;
        } else {
            return mBubbleCenterRawSolidX + mTrackLength * (mProgress - mMin) / mDelta;
        }
    }

    private float calculateProgress() {
        if (isRtl) {
            return (mRight - mThumbCenterX) * mDelta / mTrackLength + mMin;
        } else {
            return (mThumbCenterX - mLeft) * mDelta / mTrackLength + mMin;
        }
    }

    EventHandler eventHandler = new EventHandler(EventRunner.getMainEventRunner());
    /////// Api begins /////////////////////////////////////////////////////////////////////////////

    /**
     * When BubbleSeekBar's parent view is scrollable, must listener to it's scrolling and call this
     * method to correct the offsets.
     */
    public void correctOffsetWhenContainerOnScrolling() {
        if (isHideBubble)
            return;

        locatePositionInWindow();

        if (mBubbleView.getComponentParent() != null) {
            if (isAlwaysShowBubble) {
//                mLayoutParams.y = (int) (mBubbleCenterRawSolidY + 0.5f);
//                mWindowManager.updateViewLayout(mBubbleView, mLayoutParams);
            } else {
                invalidate();
            }
        }
    }

    public float getMin() {
        return mMin;
    }

    public float getMax() {
        return mMax;
    }

    public void setProgress(float progress) {
        mProgress = progress;

        if (mProgressListener != null) {
            mProgressListener.onProgressChanged(this, getProgress(), getProgressFloat(), false);
            mProgressListener.getProgressOnFinally(this, getProgress(), getProgressFloat(), false);
        }
        if (!isHideBubble) {
            mBubbleCenterRawX = calculateCenterRawXofBubbleView();
        }
        if (isAlwaysShowBubble) {
//            hideBubble();
//            eventHandler.postTask(new Runnable() {
//                @Override
//                public void run() {
//                    showBubble();
//                    triggerBubbleShowing = true;
//                }
//            }, mAlwaysShowBubbleDelay);
        }
        if (isSeekBySection) {
            triggerSeekBySection = false;
        }
        invalidate();
    }

    public int getProgress() {
        return Math.round(processProgress());
    }

    public float getProgressFloat() {
        return formatFloat(processProgress());
    }

    private float processProgress() {
        final float progress = mProgress;

        if (isSeekBySection && triggerSeekBySection) {
            float half = mSectionValue / 2;

            if (isTouchToSeek) {
                if (progress == mMin || progress == mMax) {
                    return progress;
                }

                float secValue;
                for (int i = 0; i <= mSectionCount; i++) {
                    secValue = i * mSectionValue;
                    if (secValue < progress && secValue + mSectionValue >= progress) {
                        if (secValue + half > progress) {
                            return secValue;
                        } else {
                            return secValue + mSectionValue;
                        }
                    }
                }
            }

            if (progress >= mPreSecValue) { // increasing
                if (progress >= mPreSecValue + half) {
                    mPreSecValue += mSectionValue;
                    return mPreSecValue;
                } else {
                    return mPreSecValue;
                }
            } else { // reducing
                if (progress >= mPreSecValue - half) {
                    return mPreSecValue;
                } else {
                    mPreSecValue -= mSectionValue;
                    return mPreSecValue;
                }
            }
        }

        return progress;
    }

    public OnProgressChangedListener getOnProgressChangedListener() {
        return mProgressListener;
    }

    public void setOnProgressChangedListener(OnProgressChangedListener onProgressChangedListener) {
        mProgressListener = onProgressChangedListener;
    }

    public void setTrackColor(int trackColor) {
        if (mTrackColor != trackColor) {
            mTrackColor = trackColor;
            invalidate();
        }
    }

    public void setSecondTrackColor(int secondTrackColor) {
        if (mSecondTrackColor != secondTrackColor) {
            mSecondTrackColor = secondTrackColor;
            invalidate();
        }
    }

    public void setThumbColor(int thumbColor) {
        if (mThumbColor != thumbColor) {
            mThumbColor = thumbColor;
            invalidate();
        }
    }

    public void setBubbleColor(int bubbleColor) {
        if (mBubbleColor != bubbleColor) {
            mBubbleColor = bubbleColor;
            if (mIndicator != null) {
                mIndicator.setColors(mBubbleColor, mBubbleColor);
            }
        }
    }

    public void setCustomSectionTextArray(CustomSectionTextArray customSectionTextArray) {
        mSectionTextArray = customSectionTextArray.onCustomize(mSectionCount, mSectionTextArray);
        for (int i = 0; i <= mSectionCount; i++) {
            if (mSectionTextArray.get(i) == null) {
                mSectionTextArray.put(i, "");
            }
        }

        isShowThumbText = false;
        postLayout();
        invalidate();
    }
    /////// Api ends ///////////////////////////////////////////////////////////////////////////////

    void config(BubbleConfigBuilder builder) {
        mMin = builder.min;
        mMax = builder.max;
        mProgress = builder.progress;
        isFloatType = builder.floatType;
        mTrackSize = builder.trackSize;
        mSecondTrackSize = builder.secondTrackSize;
        mThumbRadius = builder.thumbRadius;
        mThumbRadiusOnDragging = builder.thumbRadiusOnDragging;
        mTrackColor = builder.trackColor;
        mSecondTrackColor = builder.secondTrackColor;
        mThumbColor = builder.thumbColor;
        mSectionCount = builder.sectionCount;
        isShowSectionMark = builder.showSectionMark;
        isAutoAdjustSectionMark = builder.autoAdjustSectionMark;
        isShowSectionText = builder.showSectionText;
        mSectionTextSize = builder.sectionTextSize;
        mSectionTextColor = builder.sectionTextColor;
        mSectionTextPosition = builder.sectionTextPosition;
        mSectionTextInterval = builder.sectionTextInterval;
        isShowThumbText = builder.showThumbText;
        mThumbTextSize = builder.thumbTextSize;
        mThumbTextColor = builder.thumbTextColor;
        isShowProgressInFloat = builder.showProgressInFloat;
        mAnimDuration = builder.animDuration;
        isTouchToSeek = builder.touchToSeek;
        isSeekStepSection = builder.seekStepSection;
        isSeekBySection = builder.seekBySection;
        mBubbleColor = builder.bubbleColor;
        mBubbleTextSize = builder.bubbleTextSize;
        mBubbleTextColor = builder.bubbleTextColor;
        isAlwaysShowBubble = builder.alwaysShowBubble;
        mAlwaysShowBubbleDelay = builder.alwaysShowBubbleDelay;
        isHideBubble = builder.hideBubble;
        isRtl = builder.rtl;

        initConfigByPriority();
        calculateRadiusOfBubble();

        if (mProgressListener != null) {
            mProgressListener.onProgressChanged(this, getProgress(), getProgressFloat(), false);
            mProgressListener.getProgressOnFinally(this, getProgress(), getProgressFloat(), false);
        }
        if (mIndicator!=null){
            mIndicator.markerDrawableRefresh();
        }
        mConfigBuilder = null;

        postLayout();
    }

    public BubbleConfigBuilder getConfigBuilder() {
        if (mConfigBuilder == null) {
            mConfigBuilder = new BubbleConfigBuilder(getContext(), this);
        }

        mConfigBuilder.min = mMin;
        mConfigBuilder.max = mMax;
        mConfigBuilder.progress = mProgress;
        mConfigBuilder.floatType = isFloatType;
        mConfigBuilder.trackSize = mTrackSize;
        mConfigBuilder.secondTrackSize = mSecondTrackSize;
        mConfigBuilder.thumbRadius = mThumbRadius;
        mConfigBuilder.thumbRadiusOnDragging = mThumbRadiusOnDragging;
        mConfigBuilder.trackColor = mTrackColor;
        mConfigBuilder.secondTrackColor = mSecondTrackColor;
        mConfigBuilder.thumbColor = mThumbColor;
        mConfigBuilder.sectionCount = mSectionCount;
        mConfigBuilder.showSectionMark = isShowSectionMark;
        mConfigBuilder.autoAdjustSectionMark = isAutoAdjustSectionMark;
        mConfigBuilder.showSectionText = isShowSectionText;
        mConfigBuilder.sectionTextSize = mSectionTextSize;
        mConfigBuilder.sectionTextColor = mSectionTextColor;
        mConfigBuilder.sectionTextPosition = mSectionTextPosition;
        mConfigBuilder.sectionTextInterval = mSectionTextInterval;
        mConfigBuilder.showThumbText = isShowThumbText;
        mConfigBuilder.thumbTextSize = mThumbTextSize;
        mConfigBuilder.thumbTextColor = mThumbTextColor;
        mConfigBuilder.showProgressInFloat = isShowProgressInFloat;
        mConfigBuilder.animDuration = mAnimDuration;
        mConfigBuilder.touchToSeek = isTouchToSeek;
        mConfigBuilder.seekStepSection = isSeekStepSection;
        mConfigBuilder.seekBySection = isSeekBySection;
        mConfigBuilder.bubbleColor = mBubbleColor;
        mConfigBuilder.bubbleTextSize = mBubbleTextSize;
        mConfigBuilder.bubbleTextColor = mBubbleTextColor;
        mConfigBuilder.alwaysShowBubble = isAlwaysShowBubble;
        mConfigBuilder.alwaysShowBubbleDelay = mAlwaysShowBubbleDelay;
        mConfigBuilder.hideBubble = isHideBubble;
        mConfigBuilder.rtl = isRtl;

        return mConfigBuilder;
    }

//    @Override
//    protected Parcelable onSaveInstanceState() {
//        Bundle bundle = new Bundle();
//        bundle.putParcelable("save_instance", super.onSaveInstanceState());
//        bundle.putFloat("progress", mProgress);
//
//        return bundle;
//    }
//
//    @Override
//    protected void onRestoreInstanceState(Parcelable state) {
//        if (state instanceof Bundle) {
//            Bundle bundle = (Bundle) state;
//            mProgress = bundle.getFloat("progress");
//            super.onRestoreInstanceState(bundle.getParcelable("save_instance"));
//
//            if (mBubbleView != null) {
//                mBubbleView.setProgressText(isShowProgressInFloat ?
//                        String.valueOf(getProgressFloat()) : String.valueOf(getProgress()));
//            }
//            setProgress(mProgress);
//
//            return;
//        }
//
//        super.onRestoreInstanceState(state);
//    }

    /**
     * Listen to progress onChanged, onActionUp, onFinally
     */
    public interface OnProgressChangedListener {

        void onProgressChanged(BubbleSeekBar BubbleSeekBar, int progress, float progressFloat, boolean fromUser);

        void getProgressOnActionUp(BubbleSeekBar BubbleSeekBar, int progress, float progressFloat);

        void getProgressOnFinally(BubbleSeekBar BubbleSeekBar, int progress, float progressFloat, boolean fromUser);
    }

    /**
     * Listener adapter
     * <br/>
     * usage like {@link }
     */
    public static abstract class OnProgressChangedListenerAdapter implements OnProgressChangedListener {

        @Override
        public void onProgressChanged(BubbleSeekBar BubbleSeekBar, int progress, float progressFloat, boolean fromUser) {
        }

        @Override
        public void getProgressOnActionUp(BubbleSeekBar BubbleSeekBar, int progress, float progressFloat) {
        }

        @Override
        public void getProgressOnFinally(BubbleSeekBar BubbleSeekBar, int progress, float progressFloat, boolean fromUser) {
        }
    }

    /**
     * Customize the section texts under the track according to your demands by
     * call {@link #setCustomSectionTextArray(CustomSectionTextArray)}.
     */
    public interface CustomSectionTextArray {
        /**
         * <p>
         * Customization goes here.
         * </p>
         * For example:
         * <pre> public SparseArray<String> onCustomize(int sectionCount, @NonNull SparseArray<String> array) {
         *     array.clear();
         *
         *     array.put(0, "worst");
         *     array.put(4, "bad");
         *     array.put(6, "ok");
         *     array.put(8, "good");
         *     array.put(9, "great");
         *     array.put(10, "excellent");
         * }</pre>
         *
         * @param sectionCount The section count of the {@code BubbleSeekBar}.
         * @param array        The section texts array which had been initialized already. Customize
         *                     the section text by changing one element's value of the SparseArray.
         *                     The index key ∈[0, sectionCount].
         * @return The customized section texts array. Can not be {@code null}.
         */
        HashMap<Integer, String> onCustomize(int sectionCount, HashMap<Integer, String> array);
    }

    /***********************************************************************************************
     **************************************  custom bubble view  ***********************************
     **********************************************************************************************/
    private class BubbleView extends ComponentContainer implements DrawTask {

        private Paint mBubblePaint;
        private Path mBubblePath;
        private Rect mBubbleRectF;
        private Rect mRect;
        private String mProgressText = "";

        BubbleView(Context context) {
            this(context, null);
        }

        BubbleView(Context context, AttrSet attrs) {
            this(context, attrs, "0");
        }

        BubbleView(Context context, AttrSet attrs, String defStyleAttr) {
            super(context, attrs, defStyleAttr);

            mBubblePaint = new Paint();
            mBubblePaint.setAntiAlias(true);
            mBubblePaint.setTextAlign(TextAlignment.CENTER);

            mBubblePath = new Path();
            mBubbleRectF = new Rect();
            mRect = new Rect();
//            setEstimateSizeListener(new EstimateSizeListener() {
//                @Override
//                public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
//
//                    return false;
//                }
//            });
        }

        @Override
        public void invalidate() {
            super.invalidate();
            addDrawTask(this::onDraw);
        }

        @Override
        public void onDraw(Component component, Canvas canvas) {
            setEstimatedSize(3 * mBubbleRadius, 3 * mBubbleRadius);

            mBubbleRectF.set((int) (getEstimatedWidth() / 2f - mBubbleRadius), 0,
                    (int) (getEstimatedWidth() / 2f + mBubbleRadius), 2 * mBubbleRadius);

            mBubblePath.reset();
            float x0 = getEstimatedWidth() / 2f;
            float y0 = getEstimatedHeight() - mBubbleRadius / 3f;
            mBubblePath.moveTo(x0, y0);
            float x1 = (float) (getEstimatedWidth() / 2f - Math.sqrt(3) / 2f * mBubbleRadius);
            float y1 = 3 / 2f * mBubbleRadius;
            mBubblePath.quadTo(
                    x1 - dp2px(getContext(), 2), y1 - dp2px(getContext(), 2),
                    x1, y1
            );
            mBubblePath.arcTo(new RectFloat(mBubbleRectF), 150, 240);

            float x2 = (float) (getEstimatedWidth() / 2f + Math.sqrt(3) / 2f * mBubbleRadius);
            mBubblePath.quadTo(
                    x2 + dp2px(getContext(), 2), y1 - dp2px(getContext(), 2),
                    x0, y0
            );
            mBubblePath.close();

            mBubblePaint.setColor(new Color(mBubbleColor));
            canvas.drawPath(mBubblePath, mBubblePaint);

            mBubblePaint.setTextSize(mBubbleTextSize);
            mBubblePaint.setColor(new Color(mBubbleTextColor));
            mBubblePaint.getTextBounds(mProgressText);
            Paint.FontMetrics fm = mBubblePaint.getFontMetrics();
            float baseline = mBubbleRadius + (fm.descent - fm.ascent) / 2f - fm.descent;
            canvas.drawText(mBubblePaint, mProgressText, getEstimatedWidth() / 2f, baseline);
        }


        void setProgressText(String progressText) {
            if (progressText != null && !mProgressText.equals(progressText)) {
                mProgressText = progressText;
                invalidate();
            }
        }


    }

    public static abstract class NumericTransformer {
        public abstract int transform(int value);

        public String transformToString(int value) {
            return String.valueOf(value);
        }

        public boolean useStringTransform() {
            return false;
        }
    }


    private static class DefaultNumericTransformer extends NumericTransformer {
        @Override
        public int transform(int value) {
            return value;
        }
    }

    private float calculateMarkerWidth() {
        float length = 0;
        for (int i = (int) mMin; i <= mMax; i++) {
            if (mNumericTransformer.useStringTransform()) {
                Paint paint = new Paint();
                paint.setTextSize(mBubbleTextSize);
                float textLength = paint.measureText(mNumericTransformer.transformToString(i));
                if (textLength > length) {
                    length = textLength;
                }
            } else {
                Paint paint = new Paint();
                paint.setTextSize(mBubbleTextSize);
                float textLength = paint.measureText(convertValueToMessage(mNumericTransformer.transform(i)));
                if (textLength > length) {
                    length = textLength;
                }
            }
        }
        return length;
    }

    private float getDialogX() {
        int offsetX = getOffset((int) (mValue - mMin));
        int[] xy = getLocationOnScreen();
        if (xy != null && xy.length == 2) {
            return offsetX + xy[0];
        }
        return offsetX;
    }

    private float getDialogY() {
        int[] xy = getLocationOnScreen();
        if (xy != null && xy.length == 2) {
            return getHeight() / 2 + xy[1];
        }
        return getHeight() / 2;
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float touchX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                touchX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
            } else {
                touchX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return touchX;
    }

    private int getOffset(int progressIndex) {
        int all = (int) (mMax - mMin);
        float oneLength = (getWidth() - 2 * mAddedTouchBounds) * 1.0f / all;
        return (int) (progressIndex * oneLength + mAddedTouchBounds);
    }

    private int getIndexFromX(float x) {
        if (x <= mAddedTouchBounds) {
            return 0;
        } else if (x >= getWidth() - mAddedTouchBounds) {
            return (int) (mMax - mMin);
        }

        float offsetX = x - mAddedTouchBounds;
        int all = (int) (mMax - mMin);
        float oneLength = (getWidth() - 2 * mAddedTouchBounds) * 1.0f / all;
        int left = (int) (offsetX / oneLength);
        if (offsetX - left > oneLength / 2) {
            return left + 1;
        } else {
            return left;
        }
    }

    public void setNumericTransformer(NumericTransformer transformer) {
        mNumericTransformer = transformer != null ? transformer : new DefaultNumericTransformer();
//        updateProgressMessage(mValue);
    }

    private String convertValueToMessage(int value) {
        String format = mIndicatorFormatter != null ? mIndicatorFormatter : DEFAULT_FORMATTER;
        if (mFormatter == null || !mFormatter.locale().equals(Locale.getDefault())) {
            int bufferSize = format.length() + String.valueOf(mMax).length();
            if (mFormatBuilder == null) {
                mFormatBuilder = new StringBuilder(bufferSize);
            } else {
                mFormatBuilder.ensureCapacity(bufferSize);
            }
            mFormatter = new Formatter(mFormatBuilder, Locale.getDefault());
        } else {
            mFormatBuilder.setLength(0);
        }
        return mFormatter.format(format, value).toString();
    }


    public class PopupIndicator {
        private static final int ANIMATION_DURATION = 500;
        private MarkerDrawable markerDrawable;
        private PopupDialog popupDialog;
        private Component component;
        private int offsetY;
        private float radius;
        private int thumbSize;
        private AnimatorValue animatorValue = new AnimatorValue();
        private Context context;
        private float fraction;
        private float x;
        private float y;
        private String text;
        private boolean isOpen = false;
        private BubbleSeekBar BubbleSeekBar;
        private int padding;

        public PopupIndicator(BubbleSeekBar BubbleSeekBar, AttrSet attrSet, float markerLength, int thumbSize, int separation) {
            this.BubbleSeekBar = BubbleSeekBar;
            this.context = BubbleSeekBar.getContext();
            this.thumbSize = thumbSize;
            offsetY = -thumbSize - 10;
            padding = 6;
            radius = Math.max(markerLength / 2 + padding, dp2px(20,context));
//            radius = mBubbleRadius;
            markerDrawable = new MarkerDrawable();
        }
        public void markerDrawableRefresh() {
            markerDrawable=new MarkerDrawable();
        }
        public void refresh(float x, float y, String text) {
            this.x = x;
            this.y = y;
            this.text = text;
            if (component != null && isOpen) {
                component.invalidate();
            }
        }

        public boolean isOpen() {
            return isOpen;
        }

        public void setMarkLength(float markerLength) {
            radius = markerLength / 2 + padding;
            if (isOpen && component != null) {
                component.invalidate();
            }
        }

        public void setColors(int indicatorColor, int indicatorPressedColor) {
            markerDrawable.setColors(indicatorColor, indicatorPressedColor);
            if (isOpen && component != null) {
                component.invalidate();
            }
        }

        private void refreshDialog() {
            if (fraction >= 0.5f) {
                if (!isOpen) {
                    component = new Component(context);
                    component.setLayoutConfig(new ComponentContainer.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_PARENT));
                    component.addDrawTask(new Component.DrawTask() {
                        @Override
                        public void onDraw(Component component, Canvas canvas) {
                            markerDrawable.doDraw(canvas, x, y, offsetY,
                                    BubbleSeekBar.TOP_BAR_HEIGHT, text, (fraction - 0.5f) * 2, radius, thumbSize);
                        }
                    });
                    popupDialog = new PopupDialog(context, component,
                            DisplayManager.getInstance().getDefaultDisplay(getContext()).get().getAttributes().width,
                            context.getResourceManager().getDeviceCapability().screenDensity / 160 * context.getResourceManager().getDeviceCapability().height
                    );
                    popupDialog.setCustomComponent(component);
                    popupDialog.setTransparent(true);
                    popupDialog.setBackColor(new Color(0x00000000));
                    popupDialog.show();
                    isOpen = true;
                }
            } else {
                if (isOpen) {
                    isOpen = false;
                    BubbleSeekBar.invalidate();
                    popupDialog.hide();
                }
            }
        }

        public void animateToPressed(long duration) {
            if (animatorValue != null && animatorValue.isRunning()) {
                animatorValue.stop();
            }
            animatorValue.setCurveType(Animator.CurveType.LINEAR);
            animatorValue.setDuration(duration == 0 ? (long) (ANIMATION_DURATION - fraction * ANIMATION_DURATION) : duration);
            float start = fraction;
            float end = 1;
            animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float v) {
                    fraction = start + (end - start) * v;
                    refreshDialog();
                    context.getUITaskDispatcher().asyncDispatch(() -> {
                        if (component != null) {
                            component.invalidate();
                        }
                    });
                }
            });
            animatorValue.start();
        }

        public void animateToNormal() {
            if (animatorValue != null && animatorValue.isRunning()) {
                animatorValue.stop();
            }
            animatorValue.setDuration((long) (fraction * ANIMATION_DURATION));
            float start = fraction;
            float end = 0;
            animatorValue.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float v) {
                    fraction = start + (end - start) * v;
                    refreshDialog();
                    context.getUITaskDispatcher().asyncDispatch(new Runnable() {
                        @Override
                        public void run() {
                            if (component != null) {
                                component.invalidate();
                            }
                        }
                    });
                }
            });
            animatorValue.start();
        }

        private int dp2px(float dp, Context context) {
            return (int) (context.getResourceManager().getDeviceCapability().screenDensity / 160 * dp);
        }
    }


    public class MarkerDrawable {
        private static final int ANIMATION_DURATION = 250;
        private float mClosedStateSize;

        private Paint paint = new Paint();
        private Paint textPaint = new Paint();
        private int mPressedColor;
        private int mColor;
        private int indicatorElevation;
        private int textSize;
        private int textColor;
        private int shadowColor;
        private int shadowRadius;
        private int shadowDx;
        private int shadowDy;

        private AnimatorValue animatorValue = new AnimatorValue();

        public MarkerDrawable() {
            mColor = mBubbleColor;
            mPressedColor = mColor;
            indicatorElevation = 4;
            textColor = mBubbleTextColor;
            textSize = mBubbleTextSize;
            shadowColor = 0x00000000;
            shadowRadius = 1;
            shadowDx = 1;
            shadowDy = 1;
            paint.setAntiAlias(true);
            textPaint.setAntiAlias(true);
            textPaint.setColor(new Color(textColor));
            textPaint.setTextSize(textSize);
        }

        public void setColors(int color, int pressedColor) {
            mColor = color;
            mPressedColor = pressedColor;
        }

        public void doDraw(Canvas canvas, float x, float y, float offsetY, int windowsY, String text, float fraction, float radius, int thumbSize) {
            float tan45 = (float) Math.sin(Math.toRadians(45));
            float startY = y - tan45 * 2 * radius + offsetY-50;
            radius = thumbSize / 2 + (radius - thumbSize / 2) * fraction;
            y = y + (startY - y) * fraction - windowsY;
//            BlurDrawLooper blurDrawLooper = new BlurDrawLooper(indicatorElevation, 0, 0, new Color(0x10000000));
//            paint.setBlurDrawLooper(blurDrawLooper);
            paint.setColor(new Color(getAnimateColor(mColor, mPressedColor, fraction).asArgbInt()));
            Path path = new Path();
            path.moveTo(new Point(x - radius * tan45, y + radius * tan45));
            path.lineTo(new Point(x, y + radius + (radius * 2 * tan45 - radius) * fraction));
            path.lineTo(new Point(x + radius * tan45, y + radius * tan45));
            path.addArc(new RectFloat(x - radius, y - radius, x + radius, y + radius), 45, -270);
            path.close();
            canvas.drawPath(path, paint);

            if (fraction >= 0.9) {
                float sLength = textPaint.measureText(text);
//                BlurDrawLooper textBlurDrawLooper = new BlurDrawLooper(shadowRadius, shadowDx, shadowDy, new Color(shadowColor));
//                textPaint.setBlurDrawLooper(textBlurDrawLooper);
                canvas.drawText(textPaint, text, x - sLength / 2, y + textSize * 0.5f);
            }
        }

        public RgbColor getAnimateColor(int from, int to, float v) {
            RgbColor a = RgbColor.fromArgbInt(from);
            RgbColor b = RgbColor.fromArgbInt(to);
            int red = (int) (a.getRed() + (b.getRed() - a.getRed()) * v);
            int blue = (int) (a.getBlue() + (b.getBlue() - a.getBlue()) * v);
            int green = (int) (a.getGreen() + (b.getGreen() - a.getGreen()) * v);
            int alpha = (int) (a.getAlpha() + (b.getAlpha() - a.getAlpha()) * v);
            RgbColor mCurrentColorRgb = new RgbColor(red, green, blue, alpha);
            return mCurrentColorRgb;
        }

        private int dp2px(float dp, Context context) {
            return (int) (context.getResourceManager().getDeviceCapability().screenDensity / 160 * dp);
        }
    }
}
